/*
 * @(#)ReflectionTools.java
 *
 *
 *
 * ver 1.0 Jul 18, 2007 ggranum
 */
package org.testfire.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * While I changed methodoligies so that CURRENTLY I don't need this code, I think there may be a use for
 * it in the future.
 *
 * If there are no usages of this code by the alpha 1.0 release, by all means delete it.
 * - Geoff Granum
 */

public class ReflectionTools {

    /**
     * Find the Method object from a list of passed methods that matches the provided name, return type
     * and arguments.
     * @param methodName The name of the method
     * @param methods A list of possible matches.
     * @param returnKey The Key that the return object should be stored under; in this case indicates that
     * the method should return a value if not null.
     * @param args A string array of argument names.
     * @return The matching method, if one exists.
     */
    public static Method findMethod( String methodName, List<Method> methods,
                                     String returnKey,
                                     String... args ) {
        List<Method> ms = new ArrayList<Method>( methods );// don't operate on origional.
        for ( int i = 0; i < ms.size(); i++ ) {
            Method m = ms.get( i );
            if ( !m.getName().equals( methodName ) ) {
                ms.remove( m );
                i--;
            }
        }
        if ( ms.size() == 1 ) {
            return ms.get( 0 );
        }
        for ( int i = 0; i < ms.size(); i++ ) {
            Method m = ms.get( i );// void start() vs void start(String)
            if ( m.getParameterTypes().length != args.length ) {
                ms.remove( m );
                i--;
            }
        }
        if ( ms.size() == 1 ) {
            return ms.get( 0 );// will throw index out of bounds if bad XML file.
        }
        for ( int i = 0; i < ms.size(); i++ ) {
            Method m = ms.get( i );// int width() vs void width(int)
            if ( m.getReturnType() == null || returnKey == null ) {
                ms.remove( m );
                i--;
            }
        }
        if ( ms.size() == 1 ) {
            return ms.get( 0 );
        }
        for ( int j = 0; j < ms.size(); j++ ) {
            Method m = ms.get(
                j );// void sendKeys(String, boolean) vs void sendKeys(String, String)
            Class[] types = m.getParameterTypes();
            boolean valid = true;
            for ( int i = 0; i < types.length; i++ ) {// do what we can.

                Class c = types[i];
                if ( c.isPrimitive() ) {
                    if ( args[i].startsWith( "$" ) ) {//
                        continue;// can be any type.
                    }
                    else if ( !getPossiblePrimativeWrappers( args[i] ).contains( c ) ) {
                        valid = false;
                        break;
                    }
                }
                else {
                    if ( !args[i].startsWith( "$" ) ) {
                        valid = false;
                        break;// argument class is NOT primative, but actual argument is.
                    }
                }
            }
            if ( !valid ) {
                ms.remove( m );
                j--;
            }
        }

        if ( ms.size() != 1 ) {
            throw new IllegalStateException( "Could not determine method to call from"
                                             + " provided element" );
        }
        return ms.get( 0 );
    }

    /**
     * Converts a passed argument into a  string which can be placed into a .java method as an argument,
     * based on the Class type of the argument.
     * Basically wraps any string in quotes if they are needed.
     *
     * @param type The class of the argument
     * @param arg the argument.
     * @return A string representation which can be used to print a .java method argument.
     *  e.g. Math.add((int)arg) vs out.printline("arg"); wraps strings as needed.
     */
    public static String toArgumentString( Class type, String arg ) {
        String retValue;
        if ( arg.charAt( 0 ) != '$' ) {// A Value, NOT an Object.
            if ( isSingleQuotedType( type ) ) {
                retValue = "'" + arg + "'";
            }
            else if ( isDoubleQuotedType( type ) ) {
                retValue = '"' + arg + '"';
            }
            else {
                // some other primative, such as long, int, double etc. could blow up, but we aren't
                //  aiming for perfection here.
                retValue = arg;
            }
        }
        else if ( isPrimitive( type ) && arg.charAt( 0 ) != '$' ) {
            retValue = "(" + type.getName() + ")"
                       + arg;// God a 'primitive' interface (or even a contract) would be nice.
        }
        else {// arg is not a value, and type is either primitive or not
            retValue = "(" + type.getName() + ")" + arg.substring( 1 );// remove the '$'
        }
        return retValue;
    }

    private static boolean isDoubleQuotedType( Class c ) {
        return CharSequence.class.isAssignableFrom( c );
    }

    private static boolean isSingleQuotedType( Class c ) {
        return c == Character.class;
    }

    private static boolean isPrimitive( Class c ) {
        return c.isPrimitive() && c != Void.TYPE;
    }

    /**
     * Thanks to the lack of a 'Primative' interface, this method must exist.
     *
     * @param probablyPrimitive The object.
     * @return If the object is a boolean, String, char, int, long, etc.
     */
    private static boolean isPrimitive( Object probablyPrimitive ) {
        return probablyPrimitive != null && isPrimitive( probablyPrimitive.getClass() );
    }

    public static List<Class> getPossiblePrimativeWrappers( String argument ) {
        List<Class> list = new ArrayList<Class>();
        if ( argument == null ) {
            return list;
        }
        list.add( String.class );// obviously.
        char[] cs = argument.toCharArray();
        boolean isInt = true;
        boolean isFloat = true;
        for ( char c : cs ) {
            if ( !Character.isDigit( c ) ) {
                if ( c != '.'
                     || !isInt ) {// isInt will be false if we've already seen a '.'
                    isFloat = false;
                    break;
                }
                isInt = false;
            }
        }
        if ( isInt ) {
            list.add( Integer.class );
            list.add( Long.class );
        }
        else if ( isFloat ) {
            list.add( Float.class );
            list.add( Double.class );
        }
        if ( cs.length == 1 ) {
            list.add( Character.class );
        }
        return list;
    }

}

// eof: ReflectionTools.java